// CHECKSTYLE_OFF: Copyrighted to the Android Open Source Project.
/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
// CHECKSTYLE_ON

package java.io;

/**
 * A specialized {@link InputStream } for reading the contents of a byte array.
 *
 * @see ByteArrayOutputStream
 */
public class ByteArrayInputStream extends InputStream {
  /** The {@code byte} array containing the bytes to stream over. */
  protected byte[] buf;

  /** The current position within the byte array. */
  protected int pos;

  /**
   * The current mark position. Initially set to 0 or the <code>offset</code> parameter within the
   * constructor.
   */
  protected int mark;

  /** The total number of bytes initially available in the byte array {@code buf}. */
  protected int count;

  /**
   * Constructs a new {@code ByteArrayInputStream} on the byte array {@code buf}.
   *
   * @param buf the byte array to stream over.
   */
  public ByteArrayInputStream(byte[] buf) {
    this.mark = 0;
    this.buf = buf;
    this.count = buf.length;
  }

  /**
   * Constructs a new {@code ByteArrayInputStream} on the byte array {@code buf} with the initial
   * position set to {@code offset} and the number of bytes available set to {@code offset} + {@code
   * length}.
   *
   * @param buf the byte array to stream over.
   * @param offset the initial position in {@code buf} to start streaming from.
   * @param length the number of bytes available for streaming.
   */
  public ByteArrayInputStream(byte[] buf, int offset, int length) {
    this.buf = buf;
    pos = offset;
    mark = offset;
    count = offset + length > buf.length ? buf.length : offset + length;
  }

  /**
   * Returns the number of remaining bytes.
   *
   * @return {@code count - pos}
   */
  @Override
  public int available() {
    return count - pos;
  }

  /**
   * Closes this stream and frees resources associated with this stream.
   *
   * @throws IOException if an I/O error occurs while closing this stream.
   */
  @Override
  public void close() throws IOException {
    // Do nothing on close, this matches JDK behavior.
  }

  /**
   * Sets a mark position in this ByteArrayInputStream. The parameter {@code readlimit} is ignored.
   * Sending {@code reset()} will reposition the stream back to the marked position.
   *
   * @param readlimit ignored.
   * @see #markSupported()
   * @see #reset()
   */
  @Override
  public void mark(int readlimit) {
    mark = pos;
  }

  /**
   * Indicates whether this stream supports the {@code mark()} and {@code reset()} methods. Returns
   * {@code true} since this class supports these methods.
   *
   * @return always {@code true}.
   * @see #mark(int)
   * @see #reset()
   */
  @Override
  public boolean markSupported() {
    return true;
  }

  /**
   * Reads a single byte from the source byte array and returns it as an integer in the range from 0
   * to 255. Returns -1 if the end of the source array has been reached.
   *
   * @return the byte read or -1 if the end of this stream has been reached.
   */
  @Override
  public int read() {
    return pos < count ? buf[pos++] & 0xFF : -1;
  }

  @Override
  public int read(byte[] buffer, int byteOffset, int byteCount) {
    IOUtils.checkOffsetAndCount(buffer, byteOffset, byteCount);

    // Are there any bytes available?
    if (this.pos >= this.count) {
      return -1;
    }
    if (byteCount == 0) {
      return 0;
    }

    int copylen = this.count - pos < byteCount ? this.count - pos : byteCount;
    System.arraycopy(this.buf, pos, buffer, byteOffset, copylen);
    pos += copylen;
    return copylen;
  }

  /**
   * Resets this stream to the last marked location. This implementation resets the position to
   * either the marked position, the start position supplied in the constructor or 0 if neither has
   * been provided.
   *
   * @see #mark(int)
   */
  @Override
  public void reset() {
    pos = mark;
  }

  /**
   * Skips {@code byteCount} bytes in this InputStream. Subsequent calls to {@code read} will not
   * return these bytes unless {@code reset} is used. This implementation skips {@code byteCount}
   * number of bytes in the target stream. It does nothing and returns 0 if {@code byteCount} is
   * negative.
   *
   * @return the number of bytes actually skipped.
   */
  @Override
  public long skip(long byteCount) {
    if (byteCount <= 0) {
      return 0;
    }
    int temp = pos;
    pos = this.count - pos < byteCount ? this.count : (int) (pos + byteCount);
    return pos - temp;
  }
}
